/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Mobile;

import Bits.CodingMessage;
import Bits.Message;
import Burst.RACH;
import Network.Network;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

/**
 *
 * @author Javinel
 */
public final class Mobile implements MobileAble{
    private static int counter=0;
    private int id; 
    private int distanceToBTS;
    private double carrierFrequency;
    private String msg;
    private int slotNumber=-1;// liste des slots alloués au mobile pour communiquer ou envoyer des data
    private int startFrameNumber;
    private double timingAdvance;
    private BitSet correspondingBits;// Bits correspondant au message msg
    private ArrayList<BitSet[]> communicationFrames=new ArrayList<BitSet[]>();
    private int stateMobile=0; //activé ou non
    private Network network;
    private Thread threadSendRACH;

    public Thread getThreadSendRACH() {
        return threadSendRACH;
    }

    public void setThreadSendRACH(Thread threadSendRACH) {
        this.threadSendRACH = threadSendRACH;
    }
    
    

    public synchronized Network getNetwork() {
        return network;
    }

    public synchronized void setNetwork(Network network) {
        this.network = network;
    }
    
    

    public static int getCounter() {
        return counter;
    }

    public static void setCounter(int counter) {
        Mobile.counter = counter;
    }
    
    

    public BitSet getCorrespondingBits() {
        return correspondingBits;
    }

    public void setCorrespondingBits(BitSet correspondingBits) {
        this.correspondingBits = correspondingBits;
    }
    
    private void sequenceAndInterleave(){
		byte[] bt=this.msg.getBytes();
		correspondingBits= BitSet.valueOf(bt);
		BitSet com=null;
		int offset=0;
		while(offset< this.msg.length()*8){
			com=correspondingBits.get(offset, offset+456); //segmentation
			//comOriginale.add(convertToString(com));
			BitSet[] interleavingMatrix=new BitSet[8]; //8 colonnes de 57 bits chacune
			//initialisation des BitSets
			for(int i=0;i<8;i++){
				interleavingMatrix[i]=new BitSet(57);
				int indice=i;
				for(int j=0; j<57; j++){// remplissage des bitsets
					if(com.get(indice)){
						interleavingMatrix[i].set(j);
					}
					indice+=8;
				}
			}
				communicationFrames.add(interleavingMatrix); // ajout à la matrice d'interleaving
			offset+=456;
		}
	}
    
    
    
    /*
     * Les trois méthodes suivantes mettent en oeuvre l'entrelacement bit à bit lorsque le type de données
     * est data.
     */
    
    //on a ainsi la possibilité dès le début de la comm d'avoir les rlcMac en entier
    public List<BitSet[]> interleaveDataFrames(List<BitSet[]> communicationDataFrame){
    	List<BitSet[]> allInterleavedFrames=new ArrayList<BitSet[]>();
    	for(BitSet[] rlcMacTable:communicationDataFrame){
    		allInterleavedFrames.add(interleaveDataCommunication(rlcMacTable));
    	}
		return allInterleavedFrames;
    }
    
    
    
    //entrelacement d'une comm de 20ms (456 bits)
    public BitSet[] interleaveDataCommunication(BitSet[] interleavingMatrix){
    	BitSet[] rlcMac=new BitSet[4];
    	for(int i=0;i<4;i++){
    		rlcMac[i]=interleaveBitTypeData(interleavingMatrix[i*2], interleavingMatrix[i*2 + 1]);// 0-1, 
    	}
		return rlcMac;
    	
    }
    
    //On prend 2 bitset, entrelace, et renvoie un de taille double.
    // la récupération dans le pdtch correspondant consistera juste à extraire la moitié
    // par la fonction bitset.get(0,56),  get(57,114).
    private BitSet interleaveBitTypeData(BitSet pair, BitSet impair){
    	//int taille=pair.size()+impair.size();
    	BitSet newOne=new BitSet(114);
    	//printBitSet(pair);
    	//printBitSet(impair);
    	int indice=0; //
    	for(int i=0;i<114;i++){
    		if(i%2==0){ // si pair, on récupère du bitset pair
    			if(pair.get(indice)){
    				newOne.set(i);
    			}
    		}else{// si impair, on récupère du bitset impair
    			if(impair.get(indice)){
    				newOne.set(i);	
    			}
    			indice++;
    		}
    		
    	}
		return newOne;
    }
    
    // Cette fonction suppose qu'on a déjà récupéré les bits aux bons endroits
    public List<String> desentrelacerBlocs(List<BitSet[]> communicationFrames){
    	List<String> communicationRetrieve=new ArrayList<String>();
		for(BitSet[] bits:communicationFrames){
			int indice=0;
			BitSet com= new BitSet(456);
			for(int i=0;i<456;i++){
				int reste=i%8;
				switch(reste){
				case 0:
					if(bits[0].get(indice)){
						com.set(i);
					}
						
					break;
				case 1:
					if(bits[1].get(indice)){
						com.set(i);
					}
					break;
				case 2:
					if(bits[2].get(indice)){
						com.set(i);
					}
					break;
				case 3:
					if(bits[3].get(indice)){
						com.set(i);
					}
					break;
				case 4:
					if(bits[4].get(indice)){
						com.set(i);
					}
					break;
				case 5:
					if(bits[5].get(indice)){
						com.set(i);
					}
					break;
				case 6:
					if(bits[6].get(indice)){
						com.set(i);
					}
					break;
				default:
					if(bits[7].get(indice)){
						com.set(i);
					}
					indice++;
					break;
				}
			}
			communicationRetrieve.add(convertToString(com));
		}
		return communicationRetrieve;
	}
    
    //Conversion d'un bitSet en chaine de caractères correspondante
    public String convertToString(BitSet b){
		String myString=null;
		try {
			myString=new String(b.toByteArray(), "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return myString;
	}
    
    //affiche le bitset sous forme de 1 et de 0
    public void printBitSet(BitSet bits){
		for(int i=0;i< bits.size()-1;i++){
			if(bits.get(i)){
				System.out.print("1");
			}else{
				System.out.print("0");
			}
		}
		System.out.println("");
	}
    
    public String chooseMessage(int id){
    	String msg;
		int reste=id%4;
		switch(reste){
		case 0:
			msg=Message.M1.getMsg();
			break;
		case 1:
			msg=Message.M2.getMsg();
			break;
		case 2:
			msg= Message.M3.getMsg();
			break;
		default:
			msg=Message.M4.getMsg();
			break;
		}
		return msg;
    }
    
    // Une communication ici est donc transmise sur 8 trames TDMA
    public List<BitSet> entrelacementVoice(List<BitSet[]> communicationFrames ){
    	List<BitSet> list= new ArrayList<BitSet>();
    	BitSet bourrage=new BitSet(57);// poru le début de la comm, initialisé à 1 partout
    	for(int i=0;i<57;i++){
    		bourrage.set(i);
    	}
    	for(int i=0; i< communicationFrames.size();i++){
    		if(i==0){// début de communication: nécessité de prévenir la réception par une séquence dans l'entête.
    			for(int j=0;j<4;j++){
    				list.add(interleaveBitTypeData(bourrage, communicationFrames.get(i)[j])); //ajout d'un bitset de longueur 114  
    			}
    		}else{
    			for(int j=0;j<4;j++){
    				list.add(interleaveBitTypeData(communicationFrames.get(i-1)[j+4], communicationFrames.get(i)[j])); //ajout d'un bitset de longueur 114  
    			}
    			
    		}
    		
    	}
    	return list;
    }
    

    public synchronized int getStateMobile() {
        return stateMobile;
    }

    public void setStateMobile(int stateMobile) {
        this.stateMobile = stateMobile;
    }

    public double getTimingAdvance() {
        return timingAdvance;
    }

    public void setTimingAdvance(double timingAdvance) {
        this.timingAdvance = timingAdvance;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getDistanceToBTS() {
        return distanceToBTS;
    }

    public void setDistanceToBTS(int distanceToBTS) {
        this.distanceToBTS = distanceToBTS;
    }

    public double getCarrierFrequency() {
        return carrierFrequency;
    }

    public void setCarrierFrequency(double carrierFrequencyDL) {
        this.carrierFrequency = carrierFrequencyDL;
    }

    public synchronized int getSlotNumber() {
        return slotNumber;
    }

    public synchronized void setSlotNumber(int slotNumber) {
        this.slotNumber = slotNumber;
    }

    public int getStartFrameNumber() {
        return startFrameNumber;
    }

    public void setStartFrameNumber(int startFrameNumber) {
        this.startFrameNumber = startFrameNumber;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public ArrayList<BitSet[]> getCommunicationFrames() {
        return communicationFrames;
    }

    public void setCommunicationFrames(ArrayList<BitSet[]> communicationFrames) {
        this.communicationFrames = communicationFrames;
    }
    
    
    
    public Mobile(){
        this.id=++Mobile.counter;
        this.distanceToBTS=10;
        this.carrierFrequency=25;
        this.timingAdvance=computeTimingAdvance();
        this.msg=this.chooseMessage(Mobile.counter);
        this.sequenceAndInterleave();
    }
    
    private double computeTimingAdvance(){
        return (double)((63*this.distanceToBTS)/35);
    }
    
    public Mobile(int distance,double freq){
        this.id=++Mobile.counter;
        this.distanceToBTS=distance;
        this.carrierFrequency=freq;
        this.timingAdvance=computeTimingAdvance();
        this.msg=this.chooseMessage(Mobile.counter);
       
        this.sequenceAndInterleave();
    }
    
    public void startCommunication(){
        this.stateMobile=1;
        this.threadSendRACH=new Thread(new ThreadSendRACH(this));
        this.threadSendRACH.start();
    }
    
    public void endCommunication(){
        this.stateMobile=0;
    }

    

    
    public void sendRACH() {
        RACH r=new RACH();
        r.setIdMobile(this.id);
        r.setRandomNumber(new CodingMessage(10));
        network.addRACH(r); 
    }
    
    public synchronized void getAGCH(Network n){ //recup des infos de l'agch envoyé par le réseau 
        
            this.slotNumber=n.getNextAGCH().getSlotNumber();
            this.startFrameNumber=n.getNextAGCH().getStartFrameNumber();
            
            //reactivation du threadAGCH du réseau 
            n.setStateThreadAGCHgenerator(1);
            
            System.out.println(this.slotNumber+" slot - mobile "+this.id+" start trame="+this.startFrameNumber);
        
    }
    
}
